package org.sqlproc.engine.impl;

import java.util.List;
import java.util.Map;

import org.sqlproc.engine.SqlOrder;

/**
 * The crate (design pattern) for all input parameters for
 * {@link org.sqlproc.engine.impl.SqlMetaStatement#process(Object, Object, List, Map)}.
 * 
 * Also can work as a context for a dynamic ANSI SQL Query generation, which the main contract is
 * {@link org.sqlproc.engine.impl.SqlMetaElement#process(SqlProcessContext)}.
 * 
 * @author <a href="mailto:Vladimir.Hudec@gmail.com">Vladimir Hudec</a>
 */
class SqlProcessContext {

    /**
     * The SQL statement dynamic parameters.
     */
    Object dynamicInputValues;
    /**
     * The SQL statement static parameters.
     */
    Object staticInputValues;
    /**
     * TThe list of ordering directives.
     */
    List<SqlOrder> order;

    /**
     * Thread local holder of the configuration object.
     */
    private static final ThreadLocal<Map<String, Object>> currentFeatures = new ThreadLocal<Map<String, Object>>();

    /**
     * Creates a new instance with empty static input values.
     * 
     * @param dynamicInputValues
     *            dynamic input values
     * @param order
     *            ordering rules
     * @param features
     *            optional features
     */
    SqlProcessContext(Object dynamicInputValues, List<SqlOrder> order, Map<String, Object> features) {
        this(dynamicInputValues, null, order, features);
    }

    /**
     * Creates a new instance.
     * 
     * @param dynamicInputValues
     *            dynamic input values
     * @param staticInputValues
     *            static input values
     * @param order
     *            ordering rules
     * @param features
     *            optional features
     */
    SqlProcessContext(Object dynamicInputValues, Object staticInputValues, List<SqlOrder> order,
            Map<String, Object> features) {
        this.dynamicInputValues = (dynamicInputValues != null) ? dynamicInputValues : new Object();
        this.staticInputValues = staticInputValues;
        this.order = order;
        set(features);
    }

    /**
     * Convenient method to obtain String feature based on the name.
     * 
     * @param name
     *            name of the feature
     * @return value of the feature
     */
    static String getFeature(String name) {
        Object o = getFeatures().get(name);
        return (o != null && o instanceof String) ? (String) o : null;
    }

    /**
     * Convenient method to obtain String array features based on the name.
     * 
     * @param name
     *            name of the feature
     * @return the array of feature values
     */
    static String[] getFeatures(String name) {
        Object o = getFeatures().get(name);
        if (o != null && o instanceof String[])
            return (String[]) o;
        if (o != null && o instanceof String)
            return new String[] { (String) o };
        return null;
    }

    /**
     * Convenient method to obtain boolean feature based on the name.
     * 
     * @param name
     *            name of the feature
     * @return value of the feature
     */
    static boolean isFeature(String name) {
        Object o = getFeatures().get(name);
        return (o != null && o instanceof Boolean && ((Boolean) o)) ? true : false;
    }

    /**
     * Convenient method to obtain Integer feature based on the name.
     * 
     * @param name
     *            name of the feature
     * @return value of the feature
     */
    static Integer getFeatureAsInt(String name) {
        Object o = getFeatures().get(name);
        if (o == null)
            o = getFeatures().get("DEFAULT_" + name);
        if (o == null || !(o instanceof String))
            return null;
        try {
            return Integer.parseInt((String) o);
        } catch (NumberFormatException nfe) {
            return null;
        }
    }

    /**
     * Convenient method to obtain the index of the ordering rule.
     * 
     * @param orderId
     *            order number
     * @return index of the ordering rule
     */
    int getOrderIndex(int orderId) {
        if (order == null || order.isEmpty())
            return -1;
        for (int i = 0; i < order.size(); i++) {
            SqlOrder sqlOrder = order.get(i);
            if (sqlOrder.getOrderId() == orderId)
                return i;
        }
        return -1;
    }

    /**
     * Convenient method to obtain the ordering rule based on index.
     * 
     * @param orderIndex
     *            index of the ordering rule
     * @return ordering rule
     */
    SqlOrder.Order getOrder(int orderIndex) {
        if (orderIndex < 0 || orderIndex >= order.size())
            return SqlOrder.Order.NONE;
        return order.get(orderIndex).getOrderDirrection();
    }

    /**
     * Returns the features for current thread.
     * 
     * @return the current thread's features
     */
    static Map<String, Object> getFeatures() {
        final Map<String, Object> features = currentFeatures.get();
        if (features == null) {
            throw new RuntimeException("There are no features attached to current thread "
                    + Thread.currentThread().getName());
        }
        return features;
    }

    /**
     * THIS METHOD IS NOT PART OF THE SQL PROCESSOR PUBLIC API. DO NOT USE IT.
     * 
     * @param features
     *            The current features or null for this thread
     */
    static void set(final Map<String, Object> features) {
        if (currentFeatures.get() != null)
            return;
        if (features == null) {
            throw new IllegalArgumentException("Argument features can not be null");
        }
        currentFeatures.set(features);
    }
}
